package main

import (
	"chainproxy/config"
	"chainproxy/db"
	"chainproxy/log"
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/tidwall/gjson"
	"io/ioutil"
	"net/http"
	"strings"
	"time"
)

type ProxyAction int
const (
	ListAction ProxyAction = iota
	LatestAction
	InfoAction
	ModAction
	ZipAction
)

func (m ProxyAction) String() string {
	switch m {
	case ListAction :
		return "ListAction"
	case InfoAction:
		return "InfoAction"
	case LatestAction:
		return "LatestAction"
	case ModAction:
		return "ModAction"
	case ZipAction:
		return "ZipAction"
	}

	return "Unknown Action"
}

type PathArguments struct {
	Name string
	Action ProxyAction
	Version string
}

/* 拆分 URL
/github.com/gin-contrib/sse/@v/list
1, 拆分 @v
 */
func extractUrl(u string) (args *PathArguments, err error){
	args = &PathArguments{}
	// 如果以 @latest 结尾，即为查询最新版
	if strings.HasSuffix(u, "/@latest") {
		args.Action = LatestAction
		left := len("/@latest")
		args.Name = u[1: len(u) - left]
		return args, nil
	}
	s := strings.Split(u, "@v")
	if len(s) != 2 {
		return nil, fmt.Errorf("url format error.")
	}

	name := s[0]
	if strings.HasPrefix(name, "/") {
		name = name[1:]
	}
	if strings.HasSuffix(name, "/") {
		name = name[: len(name) - 1]
	}
	args.Name = name

	actField := s[1]
	if actField == "/list" {
		args.Action = ListAction
		return args, nil
	}

	var version string
	if strings.HasSuffix(actField, ".info") {
		args.Action = InfoAction
		version = actField[1: len(actField) - 4 - 1]
	} else if strings.HasSuffix(actField, ".mod") {
		args.Action = ModAction
		version = actField[1: len(actField) - 3 - 1]
	} else if strings.HasSuffix(actField, ".zip") {
		args.Action = ZipAction
		version = actField[1: len(actField) - 3 - 1]
	} else {
		return nil, fmt.Errorf("unknown action %s", actField)
	}

	args.Version = version
	return args, nil
}

func httpProxy(itype, partUrl, contentType string, c *gin.Context) ([]byte,error){
	u := upstream
	bUrl := fmt.Sprintf("%s/%s", u, partUrl)
	log.Logger.Info(bUrl)
	log.Logger.Info("proxy start.")
	resp, cost, err := tryGet(bUrl)
	log.Logger.Info("proxy done.")
	code := 500
	if resp != nil{
		code = resp.StatusCode
	}
	if err != nil {
		jsonStr := fmt.Sprintf(`{"upstream":"%s","type":"%s","res":"fail"}`, u, itype)
		ProxyResult <- gjson.Parse(jsonStr)
		c.AbortWithStatus(http.StatusInternalServerError)
		if config.GetBool("db.enable"){
			_ = db.Insert(&db.ErrorRecord{Url: bUrl, StatusCode: code, Content: err.Error()})
		}
		return nil,err
	}
	defer resp.Body.Close()

	jsonStr := fmt.Sprintf(`{"upstream":"%s","type":"%s","ttlb":%.3f,"res":"success"}`, u, itype, cost)
	ProxyResult <- gjson.Parse(jsonStr)
	body,e := ioutil.ReadAll(resp.Body)
	if e != nil{
		return nil,err
	}

	if contentType == ""{
		contentType = resp.Header.Get("Content-Type")
	}
	c.Data(code, contentType, body)
	return body, nil
}

func tryGet(bUrl string) (resp *http.Response, cost float64, err error) {
	times := config.GetInt("try.times")
	for i:=0; i<= times; i++{
		start := time.Now()
		resp, err = http.Get(bUrl)
		cost = time.Since(start).Seconds()
		if resp != nil && err == nil{
			log.Logger.Infof("Request %s successful.", bUrl)
			return
		}
		log.Logger.Warnf("Failed to request %s, err = %s", bUrl, err)
		log.Logger.Warnf("try again, max = %d, curr = %d", times, i + 1)
	}
	return
}

type VersionInfo struct {
	Version string    // version string
	Time    time.Time // commit time
}

func getRoot() string {
	return config.GetString("package.dir")
}
/*
此接口获得类似如下返回：
v1.1.1
v1.1.2
v1.1.3
v1.1.4
v1.3.0

此接口应直接请求后端
 */
func listHandler(args *PathArguments, c *gin.Context) {
	partUrl := fmt.Sprintf("%s/@v/list", args.Name)
	body, err:= httpProxy("list", partUrl, "", c)
	if !config.GetBool("db.enable"){
		return
	}
	if err != nil{
		log.Logger.Error(err)
		return
	}
	versions := string(body)
	pr := db.PackageProxyRecord{}
	isExist := db.FindOne(&pr, "name = ?", args.Name)
	pr.VersionList = versions
	if !isExist{
		pr.PackageName = args.Name
		_ = db.Insert(&pr)
		return
	}
	_ = db.UpdateAll(&pr)
}

/*
获得如下返回：
{"Version":"v1.3.0","Time":"2018-08-14T08:58:52Z"}

1，本地数据库中若有，直接返回
2，若无，请求后端，并记录返回到本地库
 */
func infoHandler(args *PathArguments, c *gin.Context) {
	partUrl := fmt.Sprintf("%s/@v/%s.info", args.Name, args.Version)
	versionInfoDeal("info", partUrl, args, c)
}

func latestHandler(args *PathArguments, c *gin.Context) {
	partUrl := fmt.Sprintf("%s/@latest", args.Name)
	versionInfoDeal("latest", partUrl, args, c)
}

//无论是.info还是latest接口，都走这一步处理
func versionInfoDeal(itype, partUrl string, args *PathArguments, c *gin.Context){
	if !config.GetBool("db.enable"){
		_, err := httpProxy(itype, partUrl, "", c)
		if err != nil{
			log.Logger.Error(err)
			c.AbortWithStatus(http.StatusInternalServerError)
			return
		}
	}
	var p db.PackageProxyRecord
	isExist, err := db.FindOneWithPreloadVersions(&p, "name = ?", args.Name)
	if err != nil{
		log.Logger.Error(err)
	}else if args.Version != "" && isExist && len(p.Versions) > 0{
		//.info接口查询
		Version, flag := findVersion(&p, args.Version)
		if flag{
			data := map[string]interface{}{}
			data["Version"] = Version.Version
			data["Time"] = Version.CommitTime
			//本地有该版本的包信息
			c.JSON(http.StatusOK, data)
			return
		}
	}

	//向上游服务器请求该版本包的信息
	body, err := httpProxy(itype, partUrl, "", c)
	if err != nil{
		log.Logger.Error(err)
		return
	}
	info := map[string]string{}
	err = json.Unmarshal(body, &info)
	if err != nil{		log.Logger.Error(err)
		return
	}
	if args.Version == "" && info["Version"] == p.LatestVersion{
		return
	}
	t, err := time.Parse("2006-01-02T15:04:05Z", info["Time"])
	if err != nil{
		log.Logger.Error(err)
	}
	if args.Version == ""{
		args.Version = info["Version"]
		p.LatestVersion = args.Version
	}
	version, flag := findVersion(&p, args.Version)
	if version == nil{
		version = &db.VersionInfo{}
	}
	version.Version = args.Version
	version.CommitTime = t
	if !flag{
		p.Versions = append(p.Versions, *version)
	}
	if !isExist{
		p.PackageName = args.Name
		_ = db.Insert(&p)
		return
	}
	_ = db.UpdateAll(&p)
}

//检查版本是否存在
func findVersion(p *db.PackageProxyRecord, version string) (*db.VersionInfo, bool) {

	for i:=0;i<len(p.Versions);i++{
		if p.Versions[i].Version != version{
			continue
		}
		return &p.Versions[i], true
	}
	return nil, false
}

// fileProxy 文件代理逻辑
func fileProxy(modname, version, filetype, contentType string, c *gin.Context) {
	fname := version + filetype
	//检查本地是否有该文件
	packageDir, hasLocal := isExist(modname, fname)
	if hasLocal{
		//将文件响应给客户端
		out, err := getFile(modname, fname)
		if err != nil{
			log.Logger.Error(err)
			c.AbortWithStatus(http.StatusInternalServerError)
			return
		}
		log.Logger.Info("file from local: ", fname)
		c.Data(http.StatusOK, "text/plain", out)
		savePackagePath(modname, packageDir)
		return
	}


	//请求上游服务器，并将文件保存到本地
	log.Logger.Info("local cache not found, downloading.", modname)
	partUrl := fmt.Sprintf("%s/@v/%s", modname, fname)
	body, err := httpProxy(strings.Split(filetype, ".")[1], partUrl, contentType, c)
	if err != nil{
		log.Logger.Error(err)
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}
	//bUrl := fmt.Sprintf("%s/%s/@v/%s", upstream, modname, fname)
	//resp, err := tryGet(bUrl)
	//code := 500
	//if resp != nil{
	//	code = resp.StatusCode
	//}
	//if err != nil {
	//	c.AbortWithStatus(http.StatusInternalServerError)
	//	if !config.GetBool("db.enable"){
	//		_ = db.Insert(&db.ErrorRecord{Url: bUrl, StatusCode: code, Content: err.Error()})
	//	}
	//	return
	//}
	//defer resp.Body.Close()
	//
	//body, err := ioutil.ReadAll(resp.Body)
	//if err != nil {
	//	log.Logger.Error("read all resp error, ", err.Error())
	//	c.AbortWithStatus(http.StatusInternalServerError)
	//	return
	//}
	//c.Data(resp.StatusCode, contentType, body)

	_, err = addFile(modname, version, filetype, body)

	if err != nil{
		log.Logger.Error("save file failed, ", err.Error())
		return
	}
	savePackagePath(modname, packageDir)
}

func savePackagePath(modname, packageDir string)  {
	if !config.GetBool("db.enable"){
		return
	}
	var p db.PackageProxyRecord
	has := db.FindOne(&p, "name = ?", modname)
	if has{
		if p.PackagePath == packageDir{
			return
		}
		p.PackagePath = packageDir
		_ = db.UpdateAll(p)
	}else {
		p.PackagePath = packageDir
		p.PackageName = modname
		_ = db.Insert(&p)
	}
}

/*
此接口，返回 mod 文件本身

1，本地文件系统中若有，则返回
2，若无，请求后端，并记录返回到本地
 */
func modHandler(args *PathArguments, c *gin.Context) {
	bUrl := fmt.Sprintf("%s/%s/@v/%s.mod", upstream, args.Name, args.Version)
	log.Logger.Info(bUrl)
	fileProxy(args.Name, args.Version, ".mod", "text/plain", c)
}

/*
此接口，返回 zip 压缩文件，需缓存
文件系统组织：
root/db/db
root/ver/hash(modname)/:version.zip
root/ver/hash(modname)/:version.mod
1，本地文件系统中，若有，直接返回
2，若无，请求后端，并保存到本地文件系统
 */
func zipHandler(args *PathArguments, c *gin.Context) {
	bUrl := fmt.Sprintf("%s/%s/@v/%s.zip", upstream, args.Name, args.Version)
	log.Logger.Info(bUrl)
	fileProxy(args.Name, args.Version, ".zip", "application/zip", c)
}

func moduleListHandler(c *gin.Context) {
	pathname := c.Param("pathname")
	args, err := extractUrl(pathname)
	if err != nil {
		fmt.Println(err)
		c.AbortWithStatus(http.StatusBadRequest)
		return
	}
	switch args.Action {
	case ListAction:
		listHandler(args, c)
	case LatestAction:
		latestHandler(args, c)
	case InfoAction:
		infoHandler(args, c)
	case ModAction:
		modHandler(args, c)
	case ZipAction:
		zipHandler(args, c)
	}
}
